/* SPDX-FileCopyrightText: 2008-2023 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup freestyle
 */

#include "BlenderFileLoader.h"

#include "DNA_meshdata_types.h"

#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "BLI_utildefines.h"

#include "BKE_attribute.hh"
#include "BKE_customdata.hh"
#include "BKE_global.h"
#include "BKE_mesh.hh"
#include "BKE_object.hh"

#include <sstream>

using blender::float3;
using blender::Span;

namespace Freestyle {

BlenderFileLoader::BlenderFileLoader(Render *re, ViewLayer *view_layer, Depsgraph *depsgraph)
{
  _re = re;
  _depsgraph = depsgraph;
  _Scene = nullptr;
  _numFacesRead = 0;
#if 0
  _minEdgeSize = DBL_MAX;
#endif
  _smooth = (view_layer->freestyle_config.flags & FREESTYLE_FACE_SMOOTHNESS_FLAG) != 0;
  _pRenderMonitor = nullptr;
}

BlenderFileLoader::~BlenderFileLoader()
{
  _Scene = nullptr;
}

NodeGroup *BlenderFileLoader::Load()
{
  if (G.debug & G_DEBUG_FREESTYLE) {
    cout << "\n===  Importing triangular meshes into Blender  ===" << endl;
  }

  // creation of the scene root node
  _Scene = new NodeGroup;

  if (_re->clip_start < 0.0f) {
    // Adjust clipping start/end and set up a Z offset when the viewport preview
    // is used with the orthographic view.  In this case, _re->clip_start is negative,
    // while Freestyle assumes that imported mesh data are in the camera coordinate
    // system with the view point located at origin [bug #36009].
    _z_near = -0.001f;
    _z_offset = _re->clip_start + _z_near;
    _z_far = -_re->clip_end + _z_offset;
  }
  else {
    _z_near = -_re->clip_start;
    _z_far = -_re->clip_end;
    _z_offset = 0.0f;
  }

  int id = 0;
  const eEvaluationMode eval_mode = DEG_get_mode(_depsgraph);

  DEGObjectIterSettings deg_iter_settings{};
  deg_iter_settings.depsgraph = _depsgraph;
  deg_iter_settings.flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY |
                            DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET | DEG_ITER_OBJECT_FLAG_VISIBLE |
                            DEG_ITER_OBJECT_FLAG_DUPLI;
  DEG_OBJECT_ITER_BEGIN (&deg_iter_settings, ob) {
    if (_pRenderMonitor && _pRenderMonitor->testBreak()) {
      break;
    }

    if ((ob->base_flag & (BASE_HOLDOUT | BASE_INDIRECT_ONLY)) ||
        (ob->visibility_flag & OB_HOLDOUT))
    {
      continue;
    }

    if (!(BKE_object_visibility(ob, eval_mode) & OB_VISIBLE_SELF)) {
      continue;
    }

    /* Evaluated metaballs will appear as mesh objects in the iterator. */
    if (ob->type == OB_MBALL) {
      continue;
    }

    Mesh *mesh = BKE_object_to_mesh(nullptr, ob, false);

    if (mesh) {
      insertShapeNode(ob, mesh, ++id);
      BKE_object_to_mesh_clear(ob);
    }
  }
  DEG_OBJECT_ITER_END;

  // Return the built scene.
  return _Scene;
}

#define CLIPPED_BY_NEAR -1
#define NOT_CLIPPED 0
#define CLIPPED_BY_FAR 1

// check if each vertex of a triangle (V1, V2, V3) is clipped by the near/far plane
// and calculate the number of triangles to be generated by clipping
int BlenderFileLoader::countClippedFaces(float v1[3], float v2[3], float v3[3], int clip[3])
{
  float *v[3];
  int numClipped, sum, numTris = 0;

  v[0] = v1;
  v[1] = v2;
  v[2] = v3;
  numClipped = sum = 0;
  for (int i = 0; i < 3; i++) {
    if (v[i][2] > _z_near) {
      clip[i] = CLIPPED_BY_NEAR;
      numClipped++;
    }
    else if (v[i][2] < _z_far) {
      clip[i] = CLIPPED_BY_FAR;
      numClipped++;
    }
    else {
      clip[i] = NOT_CLIPPED;
    }
#if 0
    if (G.debug & G_DEBUG_FREESTYLE) {
      printf("%d %s\n",
             i,
             (clip[i] == NOT_CLIPPED)     ? "not" :
             (clip[i] == CLIPPED_BY_NEAR) ? "near" :
                                            "far");
    }
#endif
    sum += clip[i];
  }
  switch (numClipped) {
    case 0:
      numTris = 1;  // triangle
      break;
    case 1:
      numTris = 2;  // tetragon
      break;
    case 2:
      if (sum == 0) {
        numTris = 3;  // pentagon
      }
      else {
        numTris = 1;  // triangle
      }
      break;
    case 3:
      if (ELEM(sum, 3, -3)) {
        numTris = 0;
      }
      else {
        numTris = 2;  // tetragon
      }
      break;
  }
  return numTris;
}

// find the intersection point C between the line segment from V1 to V2 and
// a clipping plane at depth Z (i.e., the Z component of C is known, while
// the X and Y components are unknown).
void BlenderFileLoader::clipLine(float v1[3], float v2[3], float c[3], float z)
{
  // Order v1 and v2 by Z values to make sure that clipLine(P, Q, c, z)
  // and clipLine(Q, P, c, z) gives exactly the same numerical result.
  float *p, *q;
  if (v1[2] < v2[2]) {
    p = v1;
    q = v2;
  }
  else {
    p = v2;
    q = v1;
  }
  double d[3];
  for (int i = 0; i < 3; i++) {
    d[i] = q[i] - p[i];
  }
  double t = (z - p[2]) / d[2];
  c[0] = p[0] + t * d[0];
  c[1] = p[1] + t * d[1];
  c[2] = z;
}

// clip the triangle (V1, V2, V3) by the near and far clipping plane and
// obtain a set of vertices after the clipping.  The number of vertices
// is at most 5.
void BlenderFileLoader::clipTriangle(int numTris,
                                     float triCoords[][3],
                                     float v1[3],
                                     float v2[3],
                                     float v3[3],
                                     float triNormals[][3],
                                     float n1[3],
                                     float n2[3],
                                     float n3[3],
                                     bool edgeMarks[5],
                                     bool em1,
                                     bool em2,
                                     bool em3,
                                     const int clip[3])
{
  float *v[3], *n[3];
  bool em[3];
  int i, j, k;

  v[0] = v1;
  n[0] = n1;
  v[1] = v2;
  n[1] = n2;
  v[2] = v3;
  n[2] = n3;
  em[0] = em1; /* edge mark of the edge between v1 and v2 */
  em[1] = em2; /* edge mark of the edge between v2 and v3 */
  em[2] = em3; /* edge mark of the edge between v3 and v1 */
  k = 0;
  for (i = 0; i < 3; i++) {
    j = (i + 1) % 3;
    if (clip[i] == NOT_CLIPPED) {
      copy_v3_v3(triCoords[k], v[i]);
      copy_v3_v3(triNormals[k], n[i]);
      edgeMarks[k] = em[i];
      k++;
      if (clip[j] != NOT_CLIPPED) {
        clipLine(v[i], v[j], triCoords[k], (clip[j] == CLIPPED_BY_NEAR) ? _z_near : _z_far);
        copy_v3_v3(triNormals[k], n[j]);
        edgeMarks[k] = false;
        k++;
      }
    }
    else if (clip[i] != clip[j]) {
      if (clip[j] == NOT_CLIPPED) {
        clipLine(v[i], v[j], triCoords[k], (clip[i] == CLIPPED_BY_NEAR) ? _z_near : _z_far);
        copy_v3_v3(triNormals[k], n[i]);
        edgeMarks[k] = em[i];
        k++;
      }
      else {
        clipLine(v[i], v[j], triCoords[k], (clip[i] == CLIPPED_BY_NEAR) ? _z_near : _z_far);
        copy_v3_v3(triNormals[k], n[i]);
        edgeMarks[k] = em[i];
        k++;
        clipLine(v[i], v[j], triCoords[k], (clip[j] == CLIPPED_BY_NEAR) ? _z_near : _z_far);
        copy_v3_v3(triNormals[k], n[j]);
        edgeMarks[k] = false;
        k++;
      }
    }
  }
  BLI_assert(k == 2 + numTris);
  (void)numTris; /* Ignored in release builds. */
}

void BlenderFileLoader::addTriangle(LoaderState *ls,
                                    float v1[3],
                                    float v2[3],
                                    float v3[3],
                                    float n1[3],
                                    float n2[3],
                                    float n3[3],
                                    bool fm,
                                    bool em1,
                                    bool em2,
                                    bool em3)
{
  float *fv[3], *fn[3];
#if 0
  float len;
#endif
  uint i, j;
  IndexedFaceSet::FaceEdgeMark marks = 0;

  // initialize the bounding box by the first vertex
  if (ls->currentIndex == 0) {
    copy_v3_v3(ls->minBBox, v1);
    copy_v3_v3(ls->maxBBox, v1);
  }

  fv[0] = v1;
  fn[0] = n1;
  fv[1] = v2;
  fn[1] = n2;
  fv[2] = v3;
  fn[2] = n3;
  for (i = 0; i < 3; i++) {

    copy_v3_v3(ls->pv, fv[i]);
    copy_v3_v3(ls->pn, fn[i]);

    // update the bounding box
    for (j = 0; j < 3; j++) {
      if (ls->minBBox[j] > ls->pv[j]) {
        ls->minBBox[j] = ls->pv[j];
      }

      if (ls->maxBBox[j] < ls->pv[j]) {
        ls->maxBBox[j] = ls->pv[j];
      }
    }

#if 0
    len = len_v3v3(fv[i], fv[(i + 1) % 3]);
    if (_minEdgeSize > len) {
      _minEdgeSize = len;
    }
#endif

    *ls->pvi = ls->currentIndex;
    *ls->pni = ls->currentIndex;
    *ls->pmi = ls->currentMIndex;

    ls->currentIndex += 3;
    ls->pv += 3;
    ls->pn += 3;

    ls->pvi++;
    ls->pni++;
    ls->pmi++;
  }

  if (fm) {
    marks |= IndexedFaceSet::FACE_MARK;
  }
  if (em1) {
    marks |= IndexedFaceSet::EDGE_MARK_V1V2;
  }
  if (em2) {
    marks |= IndexedFaceSet::EDGE_MARK_V2V3;
  }
  if (em3) {
    marks |= IndexedFaceSet::EDGE_MARK_V3V1;
  }
  *(ls->pm++) = marks;
}

// With A, B and P indicating the three vertices of a given triangle, returns:
// 1 if points A and B are in the same position in the 3D space;
// 2 if the distance between point P and line segment AB is zero; and
// zero otherwise.
int BlenderFileLoader::testDegenerateTriangle(float v1[3], float v2[3], float v3[3])
{
  const float eps = 1.0e-6;
  const float eps_sq = eps * eps;

#if 0
  float area = area_tri_v3(v1, v2, v3);
  bool verbose = (area < 1.0e-6);
#endif

  if (equals_v3v3(v1, v2) || equals_v3v3(v2, v3) || equals_v3v3(v1, v3)) {
#if 0
    if (verbose && G.debug & G_DEBUG_FREESTYLE) {
      printf("BlenderFileLoader::testDegenerateTriangle = 1\n");
    }
#endif
    return 1;
  }
  if (dist_squared_to_line_segment_v3(v1, v2, v3) < eps_sq ||
      dist_squared_to_line_segment_v3(v2, v1, v3) < eps_sq ||
      dist_squared_to_line_segment_v3(v3, v1, v2) < eps_sq)
  {
#if 0
    if (verbose && G.debug & G_DEBUG_FREESTYLE) {
      printf("BlenderFileLoader::testDegenerateTriangle = 2\n");
    }
#endif
    return 2;
  }
#if 0
  if (verbose && G.debug & G_DEBUG_FREESTYLE) {
    printf("BlenderFileLoader::testDegenerateTriangle = 0\n");
  }
#endif
  return 0;
}

static bool testEdgeMark(Mesh *mesh, const FreestyleEdge *fed, const blender::int3 &tri, int i)
{
  const Span<blender::int2> edges = mesh->edges();
  const Span<int> corner_verts = mesh->corner_verts();
  const Span<int> corner_edges = mesh->corner_edges();

  const int corner = tri[i];
  const int corner_next = tri[(i + 1) % 3];
  const blender::int2 &edge = edges[corner_edges[corner]];

  if (!ELEM(corner_verts[corner_next], edge[0], edge[1])) {
    /* Not an edge in the original mesh before triangulation. */
    return false;
  }

  return (fed[corner_edges[corner]].flag & FREESTYLE_EDGE_MARK) != 0;
}

void BlenderFileLoader::insertShapeNode(Object *ob, Mesh *mesh, int id)
{
  using namespace blender;
  char *name = ob->id.name + 2;

  const Span<float3> vert_positions = mesh->vert_positions();
  const OffsetIndices mesh_polys = mesh->faces();
  const Span<int> corner_verts = mesh->corner_verts();

  // Compute loop triangles
  int tottri = poly_to_tri_count(mesh->faces_num, mesh->corners_num);
  blender::int3 *corner_tris = (blender::int3 *)MEM_malloc_arrayN(
      tottri, sizeof(*corner_tris), __func__);
  blender::bke::mesh::corner_tris_calc(
      vert_positions, mesh_polys, corner_verts, {corner_tris, tottri});
  const blender::Span<int> tri_faces = mesh->corner_tri_faces();
  const blender::Span<blender::float3> corner_normals = mesh->corner_normals();

  // Get other mesh data
  const FreestyleEdge *fed = (const FreestyleEdge *)CustomData_get_layer(&mesh->edge_data,
                                                                         CD_FREESTYLE_EDGE);
  const FreestyleFace *ffa = (const FreestyleFace *)CustomData_get_layer(&mesh->face_data,
                                                                         CD_FREESTYLE_FACE);

  // Compute view matrix
  Object *ob_camera_eval = DEG_get_evaluated_object(_depsgraph, RE_GetCamera(_re));
  float viewinv[4][4], viewmat[4][4];
  RE_GetCameraModelMatrix(_re, ob_camera_eval, viewinv);
  invert_m4_m4(viewmat, viewinv);

  // Compute matrix including camera transform
  float obmat[4][4], nmat[4][4];
  mul_m4_m4m4(obmat, viewmat, ob->object_to_world);
  invert_m4_m4(nmat, obmat);
  transpose_m4(nmat);

  // We count the number of triangles after the clipping by the near and far view
  // planes is applied (NOTE: mesh vertices are in the camera coordinate system).
  uint numFaces = 0;
  float v1[3], v2[3], v3[3];
  float n1[3], n2[3], n3[3], facenormal[3];
  int clip[3];
  for (int a = 0; a < tottri; a++) {
    const int3 &tri = corner_tris[a];

    copy_v3_v3(v1, vert_positions[corner_verts[tri[0]]]);
    copy_v3_v3(v2, vert_positions[corner_verts[tri[1]]]);
    copy_v3_v3(v3, vert_positions[corner_verts[tri[2]]]);

    mul_m4_v3(obmat, v1);
    mul_m4_v3(obmat, v2);
    mul_m4_v3(obmat, v3);

    v1[2] += _z_offset;
    v2[2] += _z_offset;
    v3[2] += _z_offset;

    numFaces += countClippedFaces(v1, v2, v3, clip);
  }
#if 0
  if (G.debug & G_DEBUG_FREESTYLE) {
    cout << "numFaces " << numFaces << endl;
  }
#endif
  if (numFaces == 0) {
    MEM_freeN(corner_tris);
    return;
  }

  // We allocate memory for the meshes to be imported
  NodeGroup *currentMesh = new NodeGroup;
  NodeShape *shape = new NodeShape;

  uint vSize = 3 * 3 * numFaces;
  float *vertices = new float[vSize];
  uint nSize = vSize;
  float *normals = new float[nSize];
  uint *numVertexPerFaces = new uint[numFaces];
  vector<Material *> meshMaterials;
  vector<FrsMaterial> meshFrsMaterials;

  IndexedFaceSet::TRIANGLES_STYLE *faceStyle = new IndexedFaceSet::TRIANGLES_STYLE[numFaces];
  uint i;
  for (i = 0; i < numFaces; i++) {
    faceStyle[i] = IndexedFaceSet::TRIANGLES;
    numVertexPerFaces[i] = 3;
  }

  IndexedFaceSet::FaceEdgeMark *faceEdgeMarks = new IndexedFaceSet::FaceEdgeMark[numFaces];

  uint viSize = 3 * numFaces;
  uint *VIndices = new uint[viSize];
  uint niSize = viSize;
  uint *NIndices = new uint[niSize];
  uint *MIndices = new uint[viSize];  // Material Indices

  LoaderState ls;
  ls.pv = vertices;
  ls.pn = normals;
  ls.pm = faceEdgeMarks;
  ls.pvi = VIndices;
  ls.pni = NIndices;
  ls.pmi = MIndices;
  ls.currentIndex = 0;
  ls.currentMIndex = 0;

  FrsMaterial tmpMat;

  const bke::AttributeAccessor attributes = mesh->attributes();
  const VArray<int> material_indices = *attributes.lookup_or_default<int>(
      "material_index", bke::AttrDomain::Face, 0);
  const VArray<bool> sharp_faces = *attributes.lookup_or_default<bool>(
      "sharp_face", bke::AttrDomain::Face, false);

  // We parse the vlak nodes again and import meshes while applying the clipping
  // by the near and far view planes.
  for (int a = 0; a < tottri; a++) {
    const int3 &tri = corner_tris[a];
    const int poly_i = tri_faces[a];
    Material *mat = BKE_object_material_get(ob, material_indices[poly_i] + 1);

    copy_v3_v3(v1, vert_positions[corner_verts[tri[0]]]);
    copy_v3_v3(v2, vert_positions[corner_verts[tri[1]]]);
    copy_v3_v3(v3, vert_positions[corner_verts[tri[2]]]);

    mul_m4_v3(obmat, v1);
    mul_m4_v3(obmat, v2);
    mul_m4_v3(obmat, v3);

    v1[2] += _z_offset;
    v2[2] += _z_offset;
    v3[2] += _z_offset;

    if (_smooth && (!sharp_faces[poly_i])) {
      copy_v3_v3(n1, corner_normals[tri[0]]);
      copy_v3_v3(n2, corner_normals[tri[1]]);
      copy_v3_v3(n3, corner_normals[tri[2]]);

      mul_mat3_m4_v3(nmat, n1);
      mul_mat3_m4_v3(nmat, n2);
      mul_mat3_m4_v3(nmat, n3);

      normalize_v3(n1);
      normalize_v3(n2);
      normalize_v3(n3);
    }
    else {
      normal_tri_v3(facenormal, v3, v2, v1);

      copy_v3_v3(n1, facenormal);
      copy_v3_v3(n2, facenormal);
      copy_v3_v3(n3, facenormal);
    }

    uint numTris = countClippedFaces(v1, v2, v3, clip);
    if (numTris == 0) {
      continue;
    }

    bool fm = (ffa) ? (ffa[poly_i].flag & FREESTYLE_FACE_MARK) != 0 : false;
    bool em1 = false, em2 = false, em3 = false;

    if (fed) {
      em1 = testEdgeMark(mesh, fed, tri, 0);
      em2 = testEdgeMark(mesh, fed, tri, 1);
      em3 = testEdgeMark(mesh, fed, tri, 2);
    }

    if (mat) {
      tmpMat.setLine(mat->line_col[0], mat->line_col[1], mat->line_col[2], mat->line_col[3]);
      tmpMat.setDiffuse(mat->r, mat->g, mat->b, 1.0f);
      tmpMat.setSpecular(mat->specr, mat->specg, mat->specb, 1.0f);
      tmpMat.setShininess(128.0f);
      tmpMat.setPriority(mat->line_priority);
    }

    if (meshMaterials.empty()) {
      meshMaterials.push_back(mat);
      meshFrsMaterials.push_back(tmpMat);
      shape->setFrsMaterial(tmpMat);
    }
    else {
      // find if the Blender material is already in the list
      uint i = 0;
      bool found = false;

      for (vector<Material *>::iterator it = meshMaterials.begin(), itend = meshMaterials.end();
           it != itend;
           it++, i++)
      {
        if (*it == mat) {
          ls.currentMIndex = i;
          found = true;
          break;
        }
      }

      if (!found) {
        meshMaterials.push_back(mat);
        meshFrsMaterials.push_back(tmpMat);
        ls.currentMIndex = meshFrsMaterials.size() - 1;
      }
    }

    float triCoords[5][3], triNormals[5][3];
    bool edgeMarks[5];  // edgeMarks[i] is for the edge between i-th and (i+1)-th vertices

    clipTriangle(
        numTris, triCoords, v1, v2, v3, triNormals, n1, n2, n3, edgeMarks, em1, em2, em3, clip);
    for (i = 0; i < numTris; i++) {
      addTriangle(&ls,
                  triCoords[0],
                  triCoords[i + 1],
                  triCoords[i + 2],
                  triNormals[0],
                  triNormals[i + 1],
                  triNormals[i + 2],
                  fm,
                  (i == 0) ? edgeMarks[0] : false,
                  edgeMarks[i + 1],
                  (i == numTris - 1) ? edgeMarks[i + 2] : false);
      _numFacesRead++;
    }
  }

  MEM_freeN(corner_tris);

  // We might have several times the same vertex. We want a clean
  // shape with no real-vertex. Here, we are making a cleaning pass.
  float *cleanVertices = nullptr;
  uint cvSize;
  uint *cleanVIndices = nullptr;

  GeomCleaner::CleanIndexedVertexArray(
      vertices, vSize, VIndices, viSize, &cleanVertices, &cvSize, &cleanVIndices);

  float *cleanNormals = nullptr;
  uint cnSize;
  uint *cleanNIndices = nullptr;

  GeomCleaner::CleanIndexedVertexArray(
      normals, nSize, NIndices, niSize, &cleanNormals, &cnSize, &cleanNIndices);

  // format materials array
  FrsMaterial **marray = new FrsMaterial *[meshFrsMaterials.size()];
  uint mindex = 0;
  for (vector<FrsMaterial>::iterator m = meshFrsMaterials.begin(), mend = meshFrsMaterials.end();
       m != mend;
       ++m)
  {
    marray[mindex] = new FrsMaterial(*m);
    ++mindex;
  }

  // deallocates memory:
  delete[] vertices;
  delete[] normals;
  delete[] VIndices;
  delete[] NIndices;

  // Fix for degenerated triangles
  // A degenerate triangle is a triangle such that
  // 1) A and B are in the same position in the 3D space; or
  // 2) the distance between point P and line segment AB is zero.
  // Only those degenerate triangles in the second form are resolved here
  // by adding a small offset to P, whereas those in the first form are
  // addressed later in WShape::MakeFace().
  vector<detri_t> detriList;
  Vec3r zero(0.0, 0.0, 0.0);
  uint vi0, vi1, vi2;
  for (i = 0; i < viSize; i += 3) {
    detri_t detri;
    vi0 = cleanVIndices[i];
    vi1 = cleanVIndices[i + 1];
    vi2 = cleanVIndices[i + 2];
    Vec3r v0(cleanVertices[vi0], cleanVertices[vi0 + 1], cleanVertices[vi0 + 2]);
    Vec3r v1(cleanVertices[vi1], cleanVertices[vi1 + 1], cleanVertices[vi1 + 2]);
    Vec3r v2(cleanVertices[vi2], cleanVertices[vi2 + 1], cleanVertices[vi2 + 2]);
    if (v0 == v1 || v0 == v2 || v1 == v2) {
      continue;  // do nothing for now
    }
    if (GeomUtils::distPointSegment<Vec3r>(v0, v1, v2) < 1.0e-6) {
      detri.viP = vi0;
      detri.viA = vi1;
      detri.viB = vi2;
    }
    else if (GeomUtils::distPointSegment<Vec3r>(v1, v0, v2) < 1.0e-6) {
      detri.viP = vi1;
      detri.viA = vi0;
      detri.viB = vi2;
    }
    else if (GeomUtils::distPointSegment<Vec3r>(v2, v0, v1) < 1.0e-6) {
      detri.viP = vi2;
      detri.viA = vi0;
      detri.viB = vi1;
    }
    else {
      continue;
    }

    detri.v = zero;
    detri.n = 0;
    for (uint j = 0; j < viSize; j += 3) {
      if (i == j) {
        continue;
      }
      vi0 = cleanVIndices[j];
      vi1 = cleanVIndices[j + 1];
      vi2 = cleanVIndices[j + 2];
      Vec3r v0(cleanVertices[vi0], cleanVertices[vi0 + 1], cleanVertices[vi0 + 2]);
      Vec3r v1(cleanVertices[vi1], cleanVertices[vi1 + 1], cleanVertices[vi1 + 2]);
      Vec3r v2(cleanVertices[vi2], cleanVertices[vi2 + 1], cleanVertices[vi2 + 2]);
      if (detri.viP == vi0 && (detri.viA == vi1 || detri.viB == vi1)) {
        detri.v += (v2 - v0);
        detri.n++;
      }
      else if (detri.viP == vi0 && (detri.viA == vi2 || detri.viB == vi2)) {
        detri.v += (v1 - v0);
        detri.n++;
      }
      else if (detri.viP == vi1 && (detri.viA == vi0 || detri.viB == vi0)) {
        detri.v += (v2 - v1);
        detri.n++;
      }
      else if (detri.viP == vi1 && (detri.viA == vi2 || detri.viB == vi2)) {
        detri.v += (v0 - v1);
        detri.n++;
      }
      else if (detri.viP == vi2 && (detri.viA == vi0 || detri.viB == vi0)) {
        detri.v += (v1 - v2);
        detri.n++;
      }
      else if (detri.viP == vi2 && (detri.viA == vi1 || detri.viB == vi1)) {
        detri.v += (v0 - v2);
        detri.n++;
      }
    }
    if (detri.n > 0) {
      detri.v.normalizeSafe();
    }
    detriList.push_back(detri);
  }

  if (!detriList.empty()) {
    vector<detri_t>::iterator v;
    for (v = detriList.begin(); v != detriList.end(); v++) {
      detri_t detri = (*v);
      if (detri.n == 0) {
        cleanVertices[detri.viP] = cleanVertices[detri.viA];
        cleanVertices[detri.viP + 1] = cleanVertices[detri.viA + 1];
        cleanVertices[detri.viP + 2] = cleanVertices[detri.viA + 2];
      }
      else if (detri.v.norm() > 0.0) {
        cleanVertices[detri.viP] += 1.0e-5 * detri.v.x();
        cleanVertices[detri.viP + 1] += 1.0e-5 * detri.v.y();
        cleanVertices[detri.viP + 2] += 1.0e-5 * detri.v.z();
      }
    }
    if (G.debug & G_DEBUG_FREESTYLE) {
      printf("Warning: Object %s contains %lu degenerated triangle%s (strokes may be incorrect)\n",
             name,
             ulong(detriList.size()),
             (detriList.size() > 1) ? "s" : "");
    }
  }

  // Create the IndexedFaceSet with the retrieved attributes
  IndexedFaceSet *rep;
  rep = new IndexedFaceSet(cleanVertices,
                           cvSize,
                           cleanNormals,
                           cnSize,
                           marray,
                           meshFrsMaterials.size(),
                           nullptr,
                           0,
                           numFaces,
                           numVertexPerFaces,
                           faceStyle,
                           faceEdgeMarks,
                           cleanVIndices,
                           viSize,
                           cleanNIndices,
                           niSize,
                           MIndices,
                           viSize,
                           nullptr,
                           0,
                           0);
  // sets the id of the rep
  rep->setId(Id(id, 0));
  rep->setName(ob->id.name + 2);
  rep->setLibraryPath(ob->id.lib ? ob->id.lib->filepath : "");

  const BBox<Vec3r> bbox = BBox<Vec3r>(Vec3r(ls.minBBox[0], ls.minBBox[1], ls.minBBox[2]),
                                       Vec3r(ls.maxBBox[0], ls.maxBBox[1], ls.maxBBox[2]));
  rep->setBBox(bbox);
  shape->AddRep(rep);

  currentMesh->AddChild(shape);
  _Scene->AddChild(currentMesh);
}

} /* namespace Freestyle */
